import Tone from "../core/Tone";
import "../source/TickSource";
import "../core/TimelineState";
import "../core/Emitter";
import "../core/Context";

/**
 *  @class  A sample accurate clock which provides a callback at the given rate.
 *          While the callback is not sample-accurate (it is still susceptible to
 *          loose JS timing), the time passed in as the argument to the callback
 *          is precise. For most applications, it is better to use Tone.Transport
 *          instead of the Clock by itself since you can synchronize multiple callbacks.
 *
 * 	@constructor
 *  @extends {Tone.Emitter}
 * 	@param {function} callback The callback to be invoked with the time of the audio event
 * 	@param {Frequency} frequency The rate of the callback
 * 	@example
 * //the callback will be invoked approximately once a second
 * //and will print the time exactly once a second apart.
 * var clock = new Tone.Clock(function(time){
 * 	console.log(time);
 * }, 1);
 */
Tone.Clock = function(){

	var options = Tone.defaults(arguments, ["callback", "frequency"], Tone.Clock);
	Tone.Emitter.call(this);

	/**
	 *  The callback function to invoke at the scheduled tick.
	 *  @type  {Function}
	 */
	this.callback = options.callback;

	/**
	 *  The next time the callback is scheduled.
	 *  @type {Number}
	 *  @private
	 */
	this._nextTick = 0;

	/**
	 *  The tick counter
	 *  @type  {Tone.TickSource}
	 *  @private
	 */
	this._tickSource = new Tone.TickSource(options.frequency);

	/**
	 *  The last time the loop callback was invoked
	 *  @private
	 *  @type {Number}
	 */
	this._lastUpdate = 0;

	/**
	 *  The rate the callback function should be invoked.
	 *  @type  {BPM}
	 *  @signal
	 */
	this.frequency = this._tickSource.frequency;
	this._readOnly("frequency");

	/**
	 *  The state timeline
	 *  @type {Tone.TimelineState}
	 *  @private
	 */
	this._state = new Tone.TimelineState(Tone.State.Stopped);
	//add an initial state
	this._state.setStateAtTime(Tone.State.Stopped, 0);

	/**
	 *  The loop function bound to its context.
	 *  This is necessary to remove the event in the end.
	 *  @type {Function}
	 *  @private
	 */
	this._boundLoop = this._loop.bind(this);

	//bind a callback to the worker thread
	this.context.on("tick", this._boundLoop);
};

Tone.extend(Tone.Clock, Tone.Emitter);

/**
 *  The defaults
 *  @const
 *  @type  {Object}
 */
Tone.Clock.defaults = {
	"callback" : Tone.noOp,
	"frequency" : 1,
};

/**
 *  Returns the playback state of the source, either "started", "stopped" or "paused".
 *  @type {Tone.State}
 *  @readOnly
 *  @memberOf Tone.Clock#
 *  @name state
 */
Object.defineProperty(Tone.Clock.prototype, "state", {
	get : function(){
		return this._state.getValueAtTime(this.now());
	}
});

/**
 *  Start the clock at the given time. Optionally pass in an offset
 *  of where to start the tick counter from.
 *  @param  {Time=}  time    The time the clock should start
 *  @param  {Ticks=}  offset  Where the tick counter starts counting from.
 *  @return  {Tone.Clock}  this
 */
Tone.Clock.prototype.start = function(time, offset){
	//make sure the context is started
	this.context.resume();
	//start the loop
	time = this.toSeconds(time);
	if (this._state.getValueAtTime(time) !== Tone.State.Started){
		this._state.setStateAtTime(Tone.State.Started, time);
		this._tickSource.start(time, offset);
		if (time < this._lastUpdate){
			this.emit("start", time, offset);
		}
	}
	return this;
};

/**
 *  Stop the clock. Stopping the clock resets the tick counter to 0.
 *  @param {Time} [time=now] The time when the clock should stop.
 *  @returns {Tone.Clock} this
 *  @example
 * clock.stop();
 */
Tone.Clock.prototype.stop = function(time){
	time = this.toSeconds(time);
	this._state.cancel(time);
	this._state.setStateAtTime(Tone.State.Stopped, time);
	this._tickSource.stop(time);
	if (time < this._lastUpdate){
		this.emit("stop", time);
	}
	return this;
};

/**
 *  Pause the clock. Pausing does not reset the tick counter.
 *  @param {Time} [time=now] The time when the clock should stop.
 *  @returns {Tone.Clock} this
 */
Tone.Clock.prototype.pause = function(time){
	time = this.toSeconds(time);
	if (this._state.getValueAtTime(time) === Tone.State.Started){
		this._state.setStateAtTime(Tone.State.Paused, time);
		this._tickSource.pause(time);
		if (time < this._lastUpdate){
			this.emit("pause", time);
		}
	}
	return this;
};

/**
 *  The number of times the callback was invoked. Starts counting at 0
 *  and increments after the callback was invoked.
 *  @type {Ticks}
 */
Object.defineProperty(Tone.Clock.prototype, "ticks", {
	get : function(){
		return Math.ceil(this.getTicksAtTime(this.now()));
	},
	set : function(t){
		this._tickSource.ticks = t;
	}
});

/**
 *  The time since ticks=0 that the Clock has been running. Accounts
 *  for tempo curves
 *  @type {Seconds}
 */
Object.defineProperty(Tone.Clock.prototype, "seconds", {
	get : function(){
		return this._tickSource.seconds;
	},
	set : function(s){
		this._tickSource.seconds = s;
	}
});

/**
 *  Return the elapsed seconds at the given time.
 *  @param  {Time}  time  When to get the elapsed seconds
 *  @return  {Seconds}  The number of elapsed seconds
 */
Tone.Clock.prototype.getSecondsAtTime = function(time){
	return this._tickSource.getSecondsAtTime(time);
};

/**
 * Set the clock's ticks at the given time.
 * @param  {Ticks} ticks The tick value to set
 * @param  {Time} time  When to set the tick value
 * @return {Tone.Clock}       this
 */
Tone.Clock.prototype.setTicksAtTime = function(ticks, time){
	this._tickSource.setTicksAtTime(ticks, time);
	return this;
};

/**
 * Get the clock's ticks at the given time.
 * @param  {Time} time  When to get the tick value
 * @return {Ticks}       The tick value at the given time.
 */
Tone.Clock.prototype.getTicksAtTime = function(time){
	return this._tickSource.getTicksAtTime(time);
};

/**
 * Get the time of the next tick
 * @param  {Ticks} ticks The tick number.
 * @param  {Time} before 
 * @return {Tone.Clock}       this
 */
Tone.Clock.prototype.nextTickTime = function(offset, when){
	when = this.toSeconds(when);
	var currentTick = this.getTicksAtTime(when);
	return this._tickSource.getTimeOfTick(currentTick+offset, when);
};

/**
 *  The scheduling loop.
 *  @private
 */
Tone.Clock.prototype._loop = function(){

	var startTime = this._lastUpdate;
	var endTime = this.now();
	this._lastUpdate = endTime;

	if (startTime !== endTime){
		//the state change events
		this._state.forEachBetween(startTime, endTime, function(e){
			switch (e.state){
				case Tone.State.Started : 
					var offset = this._tickSource.getTicksAtTime(e.time);
					this.emit("start", e.time, offset);
					break;
				case Tone.State.Stopped : 
					if (e.time !== 0){
						this.emit("stop", e.time);
					}
					break;
				case Tone.State.Paused :
					this.emit("pause", e.time); 
					break;
			}
		}.bind(this));
		//the tick callbacks
		this._tickSource.forEachTickBetween(startTime, endTime, function(time, ticks){
			this.callback(time, ticks);
		}.bind(this));
	}
};

/**
 *  Returns the scheduled state at the given time.
 *  @param  {Time}  time  The time to query.
 *  @return  {String}  The name of the state input in setStateAtTime.
 *  @example
 * clock.start("+0.1");
 * clock.getStateAtTime("+0.1"); //returns "started"
 */
Tone.Clock.prototype.getStateAtTime = function(time){
	time = this.toSeconds(time);
	return this._state.getValueAtTime(time);
};

/**
 *  Clean up
 *  @returns {Tone.Clock} this
 */
Tone.Clock.prototype.dispose = function(){
	Tone.Emitter.prototype.dispose.call(this);
	this.context.off("tick", this._boundLoop);
	this._writable("frequency");
	this._tickSource.dispose();
	this._tickSource = null;
	this.frequency = null;
	this._boundLoop = null;
	this._nextTick = Infinity;
	this.callback = null;
	this._state.dispose();
	this._state = null;
};

export default Tone.Clock;

